<?php
/**
 * Created by PhpStorm.
 * User: 我想有个家
 * Date: 2019/8/27
 * Time: 18:19
 */

namespace App\Lib;


use Hyperf\Contract\ConfigInterface;

class Token {
    // 加密方式
    private $cipher = 'AES-128-CBC';
    // token加密密钥
    private $key;
    // accesstoken加密向量
    private $iv;
    // refreshtoken加密向量
    private $refreshIv;
    // 管理员token加密向量
    private $adminIv;

    public function __construct(ConfigInterface $config) {
        $conf = $config->get('md.token');
        $this->key = "ky{$conf['key']}";
        $this->iv = "tk{$conf['iv']}";
        $this->refreshIv = "rf{$conf['iv']}";
        $this->adminIv = "{$conf['iv']}gd";
    }

    /**
     * 生成token
     * @param $uid int 用户ID
     * @return array
     */
    public function create(int $uid) {
        $time = time();
        $redis = Redis::get();
        // 利用redis自增,实现登录token
        $token = $redis->hIncrBy("token:u", "{$uid}", 1);
        $access_token = $this->encrypt(json_encode([
            'uid' => $uid,
            'token' => $token,
            'time' => $time + 86400
        ]), $this->iv);
        $refresh_token = $this->encrypt(json_encode([
            'uid' => $uid,
            'token' => $token,
            'time' => $time + 864000
        ]), $this->refreshIv);
        return [
            'access_token' => $access_token,
            'refresh_token' => $refresh_token,
            'exp_time' => 86400
        ];
    }

    /**
     * 生成token
     * @param $uid int 用户ID
     * @param $name string 用户名
     * @param $roles array 角色
     * @return string
     */
    public function admin($uid, $name, $roles) {
        $time = time();
        // 利用redis自增,实现登录token
        $token = Redis::get()->hIncrBy("token:a", "{$uid}", 1);
        return $this->encrypt(json_encode([
            'uid' => ['id' => $uid, 'name' => $name, 'rid' => $roles],
            'token' => $token,
            'time' => $time + 43200
        ]), $this->adminIv);
    }

    /**
     * 校验accessToken
     * @param $token
     * @return string
     */
    public function adminVerify($token) {
        return $this->verify($token, $this->adminIv, 'a');
    }

    /**
     * 校验accessToken
     * @param $token
     * @return string
     */
    public function accessTokenVerify($token) {
        return $this->verify($token, $this->iv, 'u');
    }

    /**
     * 校验refreshToken
     * @param $token
     * @return string
     */
    public function refreshTokenVerify($token) {
        return $this->verify($token, $this->refreshIv, 'u');
    }

    /**
     * 验证token是否有效
     * @param $token string token
     * @param $iv string 加密向量
     * @param $t string token类型
     * @return string invalid 无效 other 其他设备登录，否则返回 uid
     */
    private function verify($token, $iv, $t) {
        // 解密token
        $token = $this->decrypt($token, $iv);
        if ($token === false) {
            return 'invalid';
        }
        $token = json_decode($token, true);
        if (time() >= $token['time']) {
            return 'invalid';
        }
        // 查询access_token
        $redis = Redis::get();
        $k = $t == 'u' ? $token['uid'] : $token['uid']['id'];
        $accessToken = $redis->hget("token:{$t}", (string) $k);
        if (empty($accessToken)) {
            return 'invalid';
        }
        // 是否是本设备token
        if ($accessToken != $token['token']) {
            return 'other';
        }
        return $token['uid'];
    }

    /**
     * token加密
     * @param $content string
     * @param $iv
     * @return string
     */
    private function encrypt($content, $iv) {
        return trim(base64_encode(openssl_encrypt($content, $this->cipher, $this->key, 1, $iv)), '=');
    }

    /**
     * token解密
     * @param $content string
     * @param $iv
     * @return string
     */
    private function decrypt($content, $iv) {
        return openssl_decrypt(base64_decode($content), $this->cipher, $this->key, 1, $iv);
    }
}
